﻿using IoTMqttSdk;
using Powerpi.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Publishing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Microsoft.Extensions.Hosting;
using System.Text;

namespace Powerpi.Services
{
    public interface IMeterService
    {
        void ReadMeters();
    }
    public class IoTInstance : BackgroundService, IMeterService
    {
        private static SerialPortService _sp485;
        private IoTMqttClient _mqttClient;
        //private Dictionary<string, string> _dicVoice;
        AppSettings _settings;
        //public static IoTInstance Instance = null;
        public IHubContext<ClockHub> _notifyHub;
        ILogger<IoTInstance> _log;
        private CancellationTokenSource _CancelSource;

        public IoTInstance(IOptions<AppSettings> settings, IHubContext<ClockHub> notifyHub, ILogger<IoTInstance> log)
        {
            _settings = settings.Value;
            _notifyHub = notifyHub;
            _log = log;
            _CancelSource = new CancellationTokenSource();
        }
        public string[] Byte2String(byte[] bytestr)
        {
            string[] strbyte = new string[bytestr.Length];
            for (int i = 0; i < bytestr.Length; i++)
            {
                strbyte[i] = bytestr[i].ToString("X2");
            }
            return strbyte;
        }
        /// <summary>
        /// 初始化
        /// </summary>
        /// <returns></returns>
        private bool InitSerialPort()
        {
            try
            {
                _sp485 = new SerialPortService(_settings.SerailPort);

                //负责checkin的读卡器和负责进校提醒的服务相对应

                if (_sp485.IsOpen) return true;
                var result = _sp485.Start();

                _log.LogDebug($"串口[{_settings.SerailPort}]连接：{result}");

                _notifyHub.Clients.All.SendAsync("notify", $"串口[{_settings.SerailPort}]连接：{result}");

                return result == "Success";

            }
            catch (Exception ex)
            {
                _log.LogError(ex, $"InitSerialPort");
                return false;
            }
        }
        /// <summary>
        /// 保存抄表数据
        /// </summary>
        /// <param name="byteMessage"></param>
        private (string MeterCode, decimal Power) ReadElectricity(byte[] byteMessage)
        {
            string metercode = string.Empty;
            decimal power = 0M;
            //电表读数
            if (byteMessage[13] == (byte)(Convert.ToInt16("90", 16) + 0x33) && byteMessage[12] == (byte)(Convert.ToInt16("10", 16) + 0x33))
            {
                for (int i = 8; i >= 3; i--)
                {
                    metercode += byteMessage[i].ToString("X2");
                }

                int ff = ((byteMessage[16] - 0x33));
                var Opera1 = ff.ToString("X2");

                ff = ((byteMessage[15] - 0x33));
                Opera1 += ff.ToString("X2");

                ff = ((byteMessage[14] - 0x33));
                string Point = ff.ToString("X2");
                string strPoint = Point;
                //string Power = Opera1 + "." + strPoint;
                string Power1 = Opera1 + "." + strPoint;
                //Power.Write("电表：=====" + Power1);
                if (decimal.Parse(Power1) > 9000.00M)
                {
                    decimal temppower = decimal.Parse(Power1) - 9999.99M;
                    Power1 = temppower.ToString();
                }
                power = decimal.Parse(Power1);
            }

            //电表读数
            else if (byteMessage[17] == (byte)(Convert.ToInt16("90", 16) + 0x33) && byteMessage[16] == (byte)(Convert.ToInt16("10", 16) + 0x33))
            {
                for (int i = 12; i >= 7; i--)
                {
                    metercode += byteMessage[i].ToString("X2");
                }

                int ff = ((byteMessage[20] - 0x33));
                var Opera1 = ff.ToString("X2");

                ff = ((byteMessage[19] - 0x33));
                Opera1 += ff.ToString("X2");

                ff = ((byteMessage[18] - 0x33));
                string Point = ff.ToString("X2");
                string strPoint = Point;
                string Power1 = Opera1 + "." + strPoint;

                if (decimal.Parse(Power1) > 9000.00M)
                {
                    decimal temppower = decimal.Parse(Power1) - 9999.99M;
                    Power1 = temppower.ToString();
                }

                power = decimal.Parse(Power1);

            }

            //电表读数
            else if (byteMessage[15] == (byte)(Convert.ToInt16("90", 16) + 0x33) && byteMessage[14] == (byte)(Convert.ToInt16("10", 16) + 0x33))
            {

                for (int i = 10; i >= 5; i--)
                {
                    metercode += byteMessage[i].ToString("X2");
                }

                int ff = ((byteMessage[18] - 0x33));
                var Opera1 = ff.ToString("X2");

                ff = ((byteMessage[17] - 0x33));
                Opera1 += ff.ToString("X2");

                ff = ((byteMessage[16] - 0x33));
                string Point = ff.ToString("X2");
                string strPoint = Point;
                string Power1 = Opera1 + "." + strPoint;
                if (decimal.Parse(Power1) > 9000.00M)
                {
                    decimal temppower = decimal.Parse(Power1) - 9999.99M;
                    Power1 = temppower.ToString();
                }
                power = decimal.Parse(Power1);
            }

            else
            {
                int begin = 0;
                int end = 0;
                for (int i = 0; i < byteMessage.Length; i++)
                {
                    if ((int)byteMessage[i] == 254 && begin == 0)
                    {
                        begin = i;
                    }
                    else if ((int)byteMessage[i] == 22 && end == 0)
                    {
                        end = i;
                    }


                }

                if (end > begin)
                {
                    byte[] bytetemp = new byte[end - begin + 1];
                    int point = 0;
                    for (int j = begin; j < end; j++)
                    {
                        bytetemp[point] = byteMessage[j];
                        point++;
                    }

                    for (int i = 8; i >= 3; i--)
                    {
                        metercode += byteMessage[i].ToString("X2");
                    }

                    var Opera1 = (((byteMessage[16] - 0x33) / 16) * 10 + ((byteMessage[16] - 0x33) % 16)) * 100;
                    Opera1 += ((byteMessage[15] - 0x33) / 16) * 10 + ((byteMessage[15] - 0x33) % 16);

                    power = Opera1;
                }

            }
            return (metercode, power);
        }

        private void Connect2IoT()
        {

            if (_mqttClient == null)
            {
                _mqttClient = new IoTMqttClient(_settings.DeviceId, 0, _settings.DeviceSecret, "rootcert.pem", _settings.IoTAddress, _settings.IoTPort);
            }

            if (!_mqttClient.IsConnected)
            {
                var ret = _mqttClient.ConnectServerAsync(data =>
                {
                    // todo 业务逻辑
                    //上电、下电、读表 Power
                    var message = string.Empty;
                    var body = new Dictionary<string, dynamic> { { "status", 1 } };
                    //配置信息，命令信息
                    if (data.ServiceId == "PowerSet")
                    {
                        if (data.Paras.ContainsKey(""))
                        {
                            var a = data.Paras[""];
                        }
                    }
                    else if (data.ServiceId == "PowerCmd")
                    {
                        byte[] buffer = new byte[100];
                        byte[] receive = new byte[100];
                        if (!_sp485.IsOpen)
                        {
                            if (!InitSerialPort())
                            {
                                //端口打开失败，提交记录
                            }
                        }
                        //等待计数
                        int count = 0;
                        while (_sp485.IsBusy && count < _settings.Timeout) { count++; Task.Delay(10).Wait(); }
                        switch (data.Cmd)
                        {
                            case "READ":
                                buffer = CopyRemainPower("9010", data.Paras["MeterCode"], "93122600");
                                receive = _sp485.Send(buffer);
                                var (MeterCode, Power) = ReadElectricity(receive);
                                body.Add("power", Power);
                                body.Add("MeterCode", MeterCode);
                                break;

                            case "OFF":
                                buffer = CartPower("C03C", data.Paras["MeterCode"], "93122600");
                                receive = _sp485.Send(buffer);
                                break;
                            case "ON":
                                buffer = UpPower("C03D", data.Paras["MeterCode"], "93122600");
                                receive = _sp485.Send(buffer);

                                break;
                        }
                    }

                    _ = _mqttClient.CommandResponseAsync(data.Mid, body);
                    message = JsonConvert.SerializeObject(data);
                    _log.LogDebug($"收到命令：{message}");
                    _notifyHub.Clients.All.SendAsync("notify", $"收到命令：{message}");

                }, () =>
                {
                    _log.LogWarning("IoT设备断开了,开始重新连接");

                    _notifyHub.Clients.All.SendAsync("notify", $"IoT设备断开了");
                }).ConfigureAwait(false).GetAwaiter().GetResult();

                _log.LogDebug($"IoT设备[{_settings.DeviceId}]连接：{ret}");
            }

        }
        private void PublishData2IoT(bool success, string message, string cmd, string metercode, decimal power)
        {
            if (_mqttClient.IsConnected == false)
            {
                _log.LogWarning("IoT已断开，重连...");
                _notifyHub.Clients.All.SendAsync("notify", $"IoT已断开，重连...");

                _mqttClient.Reconnect().Wait();
                _log.LogDebug($"IoT设备状态:{_mqttClient.IsConnected}");
            }
            var reportmsg = new SendMessage
            {
                Data = new List<Service>
                {
                    new Service
                    {
                        ServiceData = new Dictionary<string, dynamic>
                        {
                            { "Result", success ? 1 : 0 },
                            { "Message", message},
                            { "ExecuteDate",DateTime.Now },
                            { "Command", cmd },
                            { "Power", power },
                            { "MeterCode", metercode }
                        },
                        ServiceId = "Report",
                        EventTime = DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ")
                    }
                }
            };
            var result = _mqttClient.ReportingDataAsync(reportmsg).GetAwaiter().GetResult();

            if (MqttClientPublishReasonCode.Success != result)
                _log.LogError($"上报信息失败：{JsonConvert.SerializeObject(reportmsg)}");
            _log.LogDebug($"上报信息：{JsonConvert.SerializeObject(reportmsg)},结果：{result}");
            _notifyHub.Clients.All.SendAsync("notify", result == MqttClientPublishReasonCode.Success ? "上报信息完成" : "上报信息失败");
        }

        public override Task StopAsync(CancellationToken cancellationToken)
        {
            _CancelSource.Cancel();
            return base.StopAsync(_CancelSource.Token);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override Task StartAsync(CancellationToken cancellationToken)
        {
            _CancelSource = new CancellationTokenSource();

            return base.StartAsync(_CancelSource.Token);
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _CancelSource.Token.Register(() => _log.LogWarning($"IoTInstance is stopping."));
            InitSerialPort();
            Connect2IoT();
            while (!stoppingToken.IsCancellationRequested)
            {

                //读表号：
                //发送数据：FE FE FE FE 68 AA AA AA AA AA AA 68 11 04 34 37 33 37 B6 16
                //接受数据：FE FE FE FE 68 3F 3F 40 00 10 19 02 30 68 91 0A 34 37 33 37 02 33 43 4C 35 63 80 16

                //断电模式：
                //发送数据：FE FE FE FE 68 47 00 10 19 02 30 68 04 06 6F F3 33 59 45 C6 75 16
                //接受数据：FE FE FE FE 68 47 00 10 19 02 30 68 84 00 F6 16

                //合闸模式：
                //发送数据：FE FE FE FE 68 47 00 10 19 02 30 68 04 06 70 F3 33 59 45 C6 76 16
                //接受数据：FE FE FE FE 68 47 00 10 19 02 30 68 84 00 F6 16

                //读总电量：
                //发送数据 FE FE FE FE 68 47 00 10 19 02 30 68 01 02 43 C3 7B 16
                //接受数据 FE FE FE FE 68 47 00 10 19 02 30 68 81 06 43 C3 AC 33 33 33 44 16


                //周期性读表
                //string[] readall = "FE FE FE FE 68 47 00 10 19 02 30 68 01 02 43 C3 7B 16".Split(' ');
                ////string[] readall = $"FE FE FE FE 68 {0} 68 01 02 43 C3 7B 16".Split(' ');

                //byte[] buf = new byte[readall.Length];
                //for (int i = 0; i < readall.Length; i++)
                //{
                //    buf[i] = Convert.ToByte(readall[i], 16);
                //}


                //周期性读表
                await Task.Delay(1000 * 60 * _settings.MeterInterval);
                await ReadAllMeters();

            }
            _sp485?.Close();
            await _mqttClient.DisconnectAsync();

            _log.LogError($"IoTInstance is stopped.");

        }
        /// <summary>
        /// 周期性抄表
        /// </summary>
        /// <returns></returns>
        private async Task ReadAllMeters()
        {
            var pwlan = System.IO.Path.Combine(Environment.CurrentDirectory, "Files");
            if (!Directory.Exists(pwlan))
            {
                Directory.CreateDirectory(pwlan);
                File.WriteAllText(System.IO.Path.Combine(pwlan, "meters"), "");
            }
            var meters = System.IO.File.ReadAllLines(System.IO.Path.Combine(pwlan, "meters"));
            for (int i = 0; i < meters.Length; i++)
            {
                try
                {
                    var buf = CopyRemainPower("9010", meters[i], "");
                    //buf = UpPower("C03C", "300219100047", "93122600");
                    if (!_sp485.IsOpen) InitSerialPort();
                    int count = 0;
                    while (_sp485.IsBusy && count <= 2)
                    {
                        count++;
                        Task.Delay(500).Wait();
                    }
                    var byteMessage = _sp485.Send(buf);
                    if (byteMessage?.Length > 10)
                    {
                        var (MeterCode, Power) = ReadElectricity(byteMessage);

                        var msg = string.Join(" ", Byte2String(byteMessage));
                        _log.LogDebug($"串口消息：{msg}");
                        await _notifyHub.Clients.All.SendAsync("notify", $"串口消息:{msg}");
                        //解析串口收到的内容
                        PublishData2IoT(true, "", "9010", MeterCode, Power);
                    }
                    else
                    {
                        _log.LogWarning($"电表[{meters[i]}]未接通");
                        PublishData2IoT(false, "未读到正确数据", "9010", meters[i], 0);
                    }
                }
                catch (Exception ex)
                {
                    _log.LogError(ex, "读表异常");
                    PublishData2IoT(false, ex.Message, "9010", meters[i], 0);
                }
            }
        }


        #region 电表命令
        /// <summary>
        /// 无条件断电(C03C)
        /// </summary>
        /// <param name="commond">操作符</param>
        /// <param name="metercode">电表编号</param>
        /// <param name="pwd">密码</param>
        /// <returns>byte 流</returns>
        public byte[] CartPower(string commond, string metercode, string pwd)
        {
            byte[] byteall = new byte[19];
            byteall[0] = (byte)0xFE;
            byteall[1] = (byte)0x68;

            byte[] byteAddress = MeterCode2Bytes(metercode);
            for (int i = 0; i < byteAddress.Length; i++)
            {
                byteall[i + 2] = byteAddress[i];
            }
            byteall[8] = (byte)(0x68);
            byteall[9] = (byte)(0x04);
            byteall[10] = (byte)(0x06);

            byte[] opera = Cmd2Bytes(commond);
            for (int i = 0; i < opera.Length; i++)
            {
                byteall[11 + i] = opera[i];
            }

            byte[] pwdbyte = Pwd2Bytes(pwd);
            for (int i = 0; i < pwdbyte.Length; i++)
            {
                byteall[13 + i] = pwdbyte[i];
            }

            byteall[17] = GetMol(byteall.Skip(1).ToArray());

            byteall[18] = (byte)(0x16);

            return byteall;
        }

        /// <summary>
        /// 无条件通电(C03D): 
        /// 字节长度从19增长到22位
        /// </summary>
        /// <param name="commond">操作符</param>
        /// <param name="metercode">电表编号</param>
        /// <param name="pwd">密码</param>
        /// <returns>byte 流,19</returns>
        public byte[] UpPower(string commond, string metercode, string pwd)
        {
            byte[] byteall = new byte[19];
            byteall[0] = (byte)0xFE;
            byteall[1] = (byte)0x68;

            byte[] byteAddress = MeterCode2Bytes(metercode);
            for (int i = 0; i < byteAddress.Length; i++)
            {
                byteall[i + 2] = byteAddress[i];
            }
            byteall[8] = (byte)(0x68);
            byteall[9] = (byte)(0x04);
            byteall[10] = (byte)(0x06);
            byte[] opera = Cmd2Bytes(commond);

            for (int i = 0; i < opera.Length; i++)
            {
                byteall[11 + i] = opera[i];
            }

            byte[] pwdbyte = Pwd2Bytes(pwd);

            for (int i = 0; i < pwdbyte.Length; i++)
            {
                byteall[13 + i] = pwdbyte[i];
            }

            byteall[17] = GetMol(byteall.Skip(1).ToArray());

            byteall[18] = (byte)(0x16);

            return byteall;
        }

        /// <summary>
        /// 抄剩余电量 和有功电量
        /// </summary>
        /// <param name="commond">操作数</param>
        /// <param name="metercode">电表编号</param>
        /// <param name="pwd">密码</param>
        /// <returns>byte</returns>
        public byte[] CopyRemainPower(string commond, string metercode, string pwd)
        {
            byte[] byteall = new byte[15];
            byteall[0] = (byte)0xFE;
            byteall[1] = (byte)0x68;

            byte[] byteAddress = MeterCode2Bytes(metercode);
            for (int i = 0; i < byteAddress.Length; i++)
            {
                byteall[i + 2] = byteAddress[i];
            }
            byteall[8] = (byte)(0x68);
            byteall[9] = (byte)(0x01);
            byteall[10] = (byte)(0x02);
            byte[] opera = Cmd2Bytes(commond);

            for (int i = 0; i < opera.Length; i++)
            {
                byteall[11 + i] = opera[i];
            }

            byteall[13] = GetMol(byteall.Skip(1).ToArray());

            byteall[14] = (byte)(0x16);

            return byteall;
        }
        /// <summary>
        /// 电表编号转byte
        /// </summary>
        /// <param name="meterCode">电表编号</param>
        /// <returns>byte</returns>
        private byte[] MeterCode2Bytes(string meterCode)
        {
            byte[] bytePwd = new byte[meterCode.Length / 2];
            for (int i = 0; i < meterCode.Length / 2; i++)
            {
                bytePwd[i] = Convert.ToByte(meterCode.Substring(meterCode.Length - (i + 1) * 2, 2), 16);
            }
            return bytePwd;

        }
        /// <summary>
        /// 操作命令
        /// </summary>
        /// <param name="commond"></param>
        /// <returns></returns>
        private byte[] Cmd2Bytes(string commond)
        {
            byte[] opera = new byte[2];

            opera[0] = (byte)(Convert.ToInt16(commond.Substring(commond.Length - 2), 16) + 0x33);
            opera[1] = (byte)(Convert.ToInt16(commond.Substring(0, 2), 16) + 0x33);
            return opera;
        }
        /// <summary>
        /// 密码加密转byte
        /// </summary>
        /// <param name="pwd">密码</param>
        /// <returns>byte</returns>
        private byte[] Pwd2Bytes(string pwd)
        {
            byte[] pwd1byte = new byte[4];

            pwd1byte[0] = (byte)(Convert.ToInt16(pwd.Substring(pwd.Length - 2), 16) + 0x33);
            pwd1byte[1] = (byte)(Convert.ToInt16(pwd.Substring(pwd.Length - 4, 2), 16) + 0x33);
            pwd1byte[2] = (byte)(Convert.ToInt16(pwd.Substring(pwd.Length - 6, 2), 16) + 0x33);
            pwd1byte[3] = (byte)(Convert.ToInt16(pwd.Substring(0, 2), 16) + 0x33);
            return pwd1byte;
        }
        /// <summary>
        /// 取得 从地址位后的 68开始到 校验码之前的 模 取不大于256的值
        /// </summary>
        /// <param name="all">byte[]</param>
        /// <returns>byte</returns>
        private byte GetMol(byte[] all)
        {
            byte mol;
            int aa = 0;
            for (int i = 0; i < all.Length; i++)
            {
                aa += all[i];
            }
            mol = (byte)(aa % 256);
            return mol;
        }
        /// <summary>
        /// 发起执行抄表
        /// </summary>
        public void ReadMeters()
        {
            _ = ReadAllMeters();
        }


        #endregion
    }
}
